package org.jeecg.modules.syncdata.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.jeffreyning.mybatisplus.service.IMppService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.ApiSignUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.modules.syncdata.dto.SyncDataDto;
import org.jeecg.modules.syncdata.entity.SyncCallApiLog;
import org.jeecg.modules.syncdata.properties.FkExchangeProperties;
import org.jeecg.modules.syncdata.properties.TaxAdminThdProperties;
import org.jeecg.modules.syncdata.service.SyncCallApiLogService;
import org.jeecg.modules.syncdata.service.SyncLogService;
import org.jeecg.modules.syncdata.service.SyncService;
import org.jeecg.modules.syncdata.vo.SyncResultVo;
import org.jeecg.modules.thirdpartyapi.constant.BgApiEnum;
import org.jeecg.modules.thirdpartyapi.dto.PullDto;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 同步服务实现类
 *
 * @author zhanglei01
 * @date 2022/03/08
 */
@Slf4j
@Service
public class SyncServiceImpl implements SyncService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private FkExchangeProperties fkExchangeProperties;

    @Autowired
    private TaxAdminThdProperties taxAdminThdProperties;

    @Autowired
    private SyncLogService syncLogService;

    @Autowired
    private SyncCallApiLogService syncCallApiLogService;

    private static final int PAGE_SIZE = 50;

    @Override
    public IPage<Map<String, Object>> callThirdPartyApi(String apiName, Object dto) {
        Result result = callThirdPartyApiUnUpGzip(apiName, dto);
        log.info("返回报文(解密前):{} ", JSON.toJSONString(result));
        if (!result.isSuccess()) {
            throw new JeecgBootException("接口返回失败，" + result.getMessage());
        }
        Object resultBody = result.getResult();
        //Base64转码后gzip解压
        String gzipStr = ApiSignUtil.unGzip(resultBody.toString());
        log.info("返回业务报文(解密后):{} ", gzipStr);
        Page page = JSON.parseObject(gzipStr, new TypeReference<Page>() {
        });
        return page;
    }

    @Override
    public Result callThirdPartyApiUnUpGzip(String apiName, Object dto) {
        TreeMap<String, Object> treeMap = new TreeMap<>();
        treeMap.put("appId", taxAdminThdProperties.getAppId());
        //随机数
        treeMap.put("nonceStr", ApiSignUtil.getNonceStr(8));
        //时间戳
        treeMap.put("timestamp", ApiSignUtil.getTimestamp());
        //生成签名
        String sign = ApiSignUtil.getReqSign(treeMap, taxAdminThdProperties.getAppSignKey());
        StringBuffer uriSbd = new StringBuffer();
        for (Map.Entry<String, Object> entry : treeMap.entrySet()) {
            uriSbd.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        String uriStr = uriSbd.append("sign=").append(sign).toString();
        String url = fkExchangeProperties.getAddress() + taxAdminThdProperties.getBgApiCommonUri() + apiName + "?" + uriStr;
        log.info("请求地址:{} ", url);
        log.info("请求报文:{} ", JSON.toJSONString(dto));
        ResponseEntity<Result> response = null;
        try {
            response = restTemplate.postForEntity(url, dto, Result.class);
        } catch (RestClientException e) {
            log.error("调用api服务异常，请检查通道网络！", e);
            response = ResponseEntity.noContent().build();
        }
        try {
            //保存调用记录表
            BgApiEnum bgApiEnum = BgApiEnum.valueOf(apiName);
            SyncCallApiLog syncCallApiLog = new SyncCallApiLog();
            syncCallApiLog.setToTable(bgApiEnum.getValue());
            syncCallApiLog.setUrl(url);
            syncCallApiLog.setDataJson(JSON.toJSONString(dto));
            if (response.getStatusCode() != HttpStatus.OK) {
                syncCallApiLog.setCallResultCode(String.valueOf(response.getStatusCode().value()));
                syncCallApiLog.setCallResultMsg("http调用：" + response.getStatusCode().getReasonPhrase());
            } else {
                syncCallApiLog.setCallResultCode(response.getBody().getCode().toString());
                syncCallApiLog.setCallResultMsg(StringUtils.substring(response.getBody().getMessage(), 0, 200));
            }
            syncCallApiLogService.save(syncCallApiLog);
        } catch (Exception e) {
        }
        if (response.getStatusCode() != HttpStatus.OK) {
            log.error("请求异常");
            throw new JeecgBootException("请求异常");
        }
        Result result = response.getBody();
        return result;
    }

    @Override
    public Boolean updateDataByList(List list, IMppService mppService, BgApiEnum bgApiEnum) {
        if (bgApiEnum.equals(BgApiEnum.dj_org) || bgApiEnum.equals(BgApiEnum.swzbfx_ckb)) {
            return mppService.saveOrUpdateBatch(list);
        }
        return mppService.saveOrUpdateBatchByMultiId(list);
    }

    @Override
    public void syncSingleTable(ExecutorService executor, BgApiEnum bgApiEnum, SyncDataDto syncDataDto, String dayTime) {
        try {
            //判断同步日志表，日期内是否同步完成，完成不同步
            Boolean syncResult = syncLogService.getSyncResult(bgApiEnum.getValue(), dayTime);
            if (syncResult) {
                return;
            }
            syncLogService.setSyncStart(bgApiEnum.getValue(), dayTime);
            SyncResultVo syncResultVo = syncSingleTable(executor, bgApiEnum, DateUtil.parseDate(dayTime));
            syncLogService.setSyncFinish(bgApiEnum.getValue(), dayTime, syncResultVo.getIsSuccess(), getMsg(syncResultVo.getMsg(), 200));
        } catch (Exception e) {
            log.error("{}表同步未知异常", bgApiEnum.getValue(), e);
            syncLogService.setSyncFinish(bgApiEnum.getValue(), dayTime, false, getMsg("未知异常," + e.getMessage(), 200));
        }
    }

    @Override
    public SyncResultVo syncSingleTable(ExecutorService executor, BgApiEnum bgApiEnum, DateTime dayTime) {
        SyncResultVo syncResultVo = new SyncResultVo();
        //初始请求，主要获取分页信息，以及判断接口是否正常
        PullDto pullDto = new PullDto();
        String startTime = DateUtil.beginOfDay(dayTime).toString();
        String endTime = DateUtil.endOfDay(dayTime).toString();
        pullDto.setStartTime(startTime);
        pullDto.setEndTime(endTime);
        pullDto.setPageIndex(1);
        pullDto.setPageSize(PAGE_SIZE);
        IPage<Map<String, Object>> mapIPage = null;
        try {
            mapIPage = callThirdPartyApi(bgApiEnum.name(), pullDto);
        } catch (Exception e) {
            log.error("同步: {}表，获取初始数据异常", bgApiEnum.getValue(), e);
            syncResultVo.setMsg(getMsg(e.getMessage()));
            return syncResultVo;
        }
        int pages = (int) mapIPage.getPages();
        if (pages < 1) {
            //插入同步日志表 完成
            syncResultVo.setIsSuccess(true);
            syncResultVo.setMsg("没有数据需要同步");
            return syncResultVo;
        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(bgApiEnum.getValue());
        Class<?> entityType = tableInfo.getEntityType();
        //获取表对应的Service bean服务
        String simpleName = tableInfo.getEntityType().getSimpleName();
        String serviceName = simpleName + "ServiceImpl";
        //首字母转小写
        serviceName = (new StringBuilder()).append(Character.toLowerCase(serviceName.charAt(0))).append(serviceName.substring(1)).toString();
        IMppService bean = (IMppService) SpringContextUtils.getBean(serviceName);
        if (bean == null) {
            log.error("同步: {}表，获取bean失败，{}", bgApiEnum.getValue(), serviceName);
            syncResultVo.setMsg("获取bean失败," + serviceName);
            return syncResultVo;
        }
        final CountDownLatch pagesLatch = new CountDownLatch(pages);
        //判断全部分页同步成功
        final CountDownLatch successFlag = new CountDownLatch(pages);
        Vector<String> errorMsgData = new Vector<String>();
        for (int i = 0; i < pages; i++) {
            PullDto dto = new PullDto();
            dto.setStartTime(pullDto.getStartTime());
            dto.setEndTime(pullDto.getEndTime());
            dto.setPageSize(pullDto.getPageSize());
            dto.setPageIndex(i + 1);
            executor.submit(() -> {
                try {
                    //获取数据
                    log.info("获取{}表，第{}页数据,共{}页", bgApiEnum.getValue(), dto.getPageIndex(), pages);
                    IPage<Map<String, Object>> PageDate = callThirdPartyApi(bgApiEnum.name(), dto);
                    List list = JSONArray.parseArray(JSON.toJSONString(PageDate.getRecords()), entityType);
                    //更新本地数据
                    Boolean aBoolean = updateDataByList(list, bean, bgApiEnum);
                    if (!aBoolean) {
                        log.error("同步: {}表，第{}页数据失败", bgApiEnum.getValue(), dto.getPageIndex());
                        errorMsgData.add("第" + dto.getPageIndex() + "页插表失败");
                    } else {
                        log.info("更新{}表，第{}页数据,完成,剩余{}页", bgApiEnum.getValue(), dto.getPageIndex(), pagesLatch.getCount() - 1);
                        successFlag.countDown();
                    }
                } catch (Exception e) {
                    log.error("同步: {}表，第{}页数据出错", bgApiEnum.getValue(), dto.getPageIndex(), e);
                    errorMsgData.add(getMsg("第" + dto.getPageIndex() + "页异常," + e.getMessage(), 300));
                } finally {
                    pagesLatch.countDown();
                }
            });
        }

        if (pagesLatch.getCount() > 0) {
            try {
                pagesLatch.await(2 * 60 * 1000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                log.error("阻塞线程失败", e);
            }
        }
        if (successFlag.getCount() != 0) {
            StringBuilder msg = new StringBuilder();
            for (String errorMsg : errorMsgData) {
                msg.append(errorMsg);
            }
            syncResultVo.setMsg(msg.toString());
        } else {
            syncResultVo.setIsSuccess(true);
            syncResultVo.setMsg("最后同步数" + mapIPage.getTotal());
        }
        return syncResultVo;
    }

    private static String getMsg(String message) {
        if (StringUtils.isBlank(message)) {
            return null;
        }
        String substring = StringUtils.substring(message, 0, 150);
        return substring;
    }

    private static String getMsg(String message, int size) {
        if (StringUtils.isBlank(message)) {
            return null;
        }
        String substring = StringUtils.substring(message, 0, size);
        return substring;
    }

    public static void main(String[] args) {
        BgApiEnum bgApiEnum = null;
        try {
            bgApiEnum = BgApiEnum.valueOf("dj_org");
        } catch (IllegalArgumentException e) {
        }
        System.out.println(bgApiEnum);
    }
}
